package com.game.battleship;

import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

import com.game.battleship.adapters.MoveShipsAdapter;
import com.game.battleship.data.Direction;
import com.game.battleship.data.FormationGenerator;
import com.game.battleship.data.Game;
import com.game.battleship.data.Ship;
import com.game.battleship.data.ShipCellsHolder;
import com.game.battleship.data.transformer.ShipRepresentationTransformer;
import com.game.battleship.manager.ForbiddenPositionsManager;
import com.game.battleship.rule.CellForbiddingRule;
import com.game.battleship.rule.CellForbiddingRuleSquareImpl;
import com.game.battleship.util.ShipUtil;
import com.game.battleship.data.ActivityShipComunicator;

/**
 * A UI screen for arranging mShips
 * @author Tony
 * @editor vtkhanh
 * 
 */
public class ArrangeShipsActivity extends Activity {
	public static final String EXTRA_INFO = "Ships";
	public static final String SERIAL_INFO = "Ships";

	private static final String BUNDLE_SHIPS = "BSG_ARS_SHP";
	private static final String BUNDLE_FORBIDDEN_POSITION = "BSG_ARS_FPOS";
	private static final String BUNDLE_BOARD = "BSG_ARS_BRD";
	
	/**
	 * value returned to the single player activity 
	 * when the back button is pressed
	 */
	public static final int BACK = 0;
	
	/**
	 * value returned to the single player activity 
	 * when the apply button is pressed
	 */
	public static final int ACCEPT = 1;

	/**
	 * The forbiding mRule used to determine which field is forbidden
	 */
	protected CellForbiddingRule mRule;
	/**
	 * Players mShips
	 */
	protected Ship[] mShips;
	/**
	 * manages forbidden fields when mShips are moved about
	 */
	protected ForbiddenPositionsManager mForbiddenPositions;
	
	/**
	 * the game board on which the mShips are moved around
	 */
	protected short[] mBoard;

	/**
	 * stores the selected ship
	 */
	protected ShipCellsHolder mSelectedShip;
	/**
	 * 
	 */
	protected short mSelectedShipCell;
	/**
	 * the index in the mShips array of the selected ship
	 */
	protected int mSelectedShipIndex;
	/**
	 * last possible location of the moved ship. When an imposible arrangement is attempted 
	 * the moved ship returns to this position 
	 *//*
	protected ShipCellsHolder mLastPossiblePosition;*/
	/**
	 * the origin position of moved ship
	 */
	protected ShipCellsHolder mOriginPosition;
	/**
	 * the position last position of the cursor measured in game board positions
	 */
	protected short mOldCursorPosition;

	/**
	 * ImageAdapter for moving mShips
	 */
	protected MoveShipsAdapter mMoveShipsAdapter;
	/**
	 * the board
	 */
	protected GridView boardGrid;
	/**
	 * accept button
	 */
	protected Button btnGo;
	/**
	 * cancel button
	 */
	protected Button btnCancel;
	/**
	 * rotate button
	 */
	protected Button btnRotate;
	/**
	 * the position which is able to put ship
	 */
	protected boolean mIsLegalMoving = true;
	
	private String username = "";
	
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		Bundle extras = this.getIntent().getExtras();
		if (extras != null && extras.containsKey(UserInfoActivity.EXT_USERNAME)) {
			username = extras.getString(UserInfoActivity.EXT_USERNAME);
		}
		
		if (savedInstanceState != null) {
			restoreState(savedInstanceState);
		} else {
			initGame();
		}		
	}

	/**
	 * Tries to rotate a ship
	 * @param v
	 */
	protected void rotate(View v) {
		if (mSelectedShipIndex != 0) {
			boolean isRotatable = true;
			Ship ship = mShips[mSelectedShipIndex - 1];
			ShipCellsHolder oldSFH = ShipRepresentationTransformer
					.getShipCellsHolderFromShip(ship);
			ShipCellsHolder tmpSFH = ShipRepresentationTransformer
					.getShipCellsHolderFromShip(ship);
			for (short forbiddenField : ShipUtil.getForbiddenCellsFromShip(ship, mRule)) {
				mForbiddenPositions.removeForbiddenCell(forbiddenField);
			}
			if (tmpSFH.getDirection() == Direction.HORIZONTAL) {
				int newLastFieldVerticalCoordinate;
				newLastFieldVerticalCoordinate = tmpSFH.getFirstCell()/Game.BOARD_SIDE;
				newLastFieldVerticalCoordinate += (tmpSFH.getLength() - 1);
				if(newLastFieldVerticalCoordinate >= Game.BOARD_SIDE)
					isRotatable = false;
				tmpSFH.setDirection(Direction.VERTICAL);
			} else {
				int newLastFieldHorizontalCoordinate;
				newLastFieldHorizontalCoordinate = tmpSFH.getFirstCell()%Game.BOARD_SIDE;
				newLastFieldHorizontalCoordinate += (tmpSFH.getLength() - 1);
				if(newLastFieldHorizontalCoordinate >= Game.BOARD_SIDE)
					isRotatable = false;
				tmpSFH.setDirection(Direction.HORIZONTAL);
			}
			if (isShipOk(tmpSFH) && isRotatable) {
				paint(oldSFH, mMoveShipsAdapter.getSeaPicture(),
						mMoveShipsAdapter.getSeaPicture(),
						PaintAction.RESTORE);
				for (int i : ship.getBoardCells()) {
					mBoard[i] = 0;
				}
				paint(tmpSFH, mMoveShipsAdapter.getShipPicture(),
						mMoveShipsAdapter.getShipPicture(),
						PaintAction.PAINT_OVER);
				ship = ShipRepresentationTransformer
						.getShipFromShipCellsHolder(tmpSFH);
				mShips[mSelectedShipIndex - 1] = ship;
				for (int i : ship.getBoardCells()) {
					mBoard[i] = (short) mSelectedShipIndex;
				}
			} else {
				Toast.makeText(ArrangeShipsActivity.this,
						"Last touched ship cannot be rotated.", 3000).show();
			}
			for (short forbiddenField : ShipUtil
					.getForbiddenCellsFromShip(ship, mRule)) {
				mForbiddenPositions.addForbiddenCell(forbiddenField);
			}
		}
	}

	private boolean isShipOk(ShipCellsHolder ship) {
		Ship tmpShip = ShipRepresentationTransformer
				.getShipFromShipCellsHolder(ship);
		boolean shipIsOk = true;

		for (int position : tmpShip.getBoardCells()) {
			if (position < 0 || position > Game.BOARD_SIDE * Game.BOARD_SIDE
					|| mForbiddenPositions.isForbidden((short) position)) {
				shipIsOk = false;
			}
		}
		return shipIsOk;
	}

	/**
	 * manages an OnTouchDown event
	 * @param position
	 */
	protected void manageOnTouchDown(short position) {
		mSelectedShipIndex = mBoard[position];
		Ship ship = mShips[mSelectedShipIndex - 1];		
		updateBoard(ship.getBoardCells(), Game.WATER);
		mSelectedShip = ShipRepresentationTransformer.getShipCellsHolderFromShip(ship);
		
		mOriginPosition = new ShipCellsHolder(mSelectedShip.getLength(),
				mSelectedShip.getFirstCell(), mSelectedShip.getDirection());
		if (mSelectedShip.getDirection() == Direction.HORIZONTAL) {
			mSelectedShipCell = (short) (position - mSelectedShip.getFirstCell());
		} else {
			mSelectedShipCell = (short) ((position - mSelectedShip.getFirstCell()) / Game.BOARD_SIDE);
		}
		mOldCursorPosition = position;
		for (short forbiddenCell : ShipUtil.getForbiddenCellsFromShip(ship, mRule)) {
			mForbiddenPositions.removeForbiddenCell(forbiddenCell);
		}
		paint(mSelectedShip, mMoveShipsAdapter.getSelectedShipPicture(),
				mMoveShipsAdapter.getForbiddenFieldPicture(),
				PaintAction.PAINT_OVER);
	}

	/** 
	 * manages a OnTouchMove event
	 * @param position
	 */
	protected void manageOnTouchMove(short position) {
		if (position != mOldCursorPosition) {
			paint(mSelectedShip, mMoveShipsAdapter.getSeaPicture(),
					mMoveShipsAdapter.getShipPicture(), PaintAction.RESTORE);
			int oldFirstCell = mSelectedShip.getFirstCell();
			mSelectedShip.setFirstCell(oldFirstCell += position - mOldCursorPosition);
			mOldCursorPosition = position;
			
			mIsLegalMoving = !paint(mSelectedShip, mMoveShipsAdapter.getSelectedShipPicture(),
					mMoveShipsAdapter.getForbiddenFieldPicture(),
					PaintAction.PAINT_OVER);
		}
	}

	/**
	 * Manages a OnTouchUp event
	 * @param position
	 */
	protected void manageOnTouchUp(short position) {
		Ship newShip = null;
		if (mIsLegalMoving) {
			paint(mSelectedShip, mMoveShipsAdapter.getShipPicture(), 
					mMoveShipsAdapter.getShipPicture(), PaintAction.PAINT_OVER);
			newShip = ShipRepresentationTransformer.getShipFromShipCellsHolder(mSelectedShip);
		} 
		else {
			paint(mSelectedShip, mMoveShipsAdapter.getSeaPicture(), 
					mMoveShipsAdapter.getShipPicture(), PaintAction.RESTORE);
			paint(mOriginPosition, mMoveShipsAdapter.getShipPicture(), 
					mMoveShipsAdapter.getShipPicture(), PaintAction.PAINT_OVER);
			newShip = ShipRepresentationTransformer.getShipFromShipCellsHolder(mOriginPosition);
		}
		 
		mShips[mSelectedShipIndex - 1] = newShip;		
		updateBoard(newShip.getBoardCells(), mSelectedShipIndex);
		for (short forbiddenCell : ShipUtil.getForbiddenCellsFromShip(newShip, mRule)) {
			mForbiddenPositions.addForbiddenCell(forbiddenCell);
		}
		if (!mIsLegalMoving) {
			Toast.makeText(ArrangeShipsActivity.this,
						   "Forbidden arrangement! Ship restored to previous position",
							3000).show();
		}
		mSelectedShip = mOriginPosition = null;
	}


	/**
	 * Initializes the state of the board
	 */
	protected void initGame() {
		mSelectedShipIndex = -1;
		FormationGenerator generator = new FormationGenerator();
		mShips = generator.getRandomShipsPosition();
		int[] shipCells = ShipUtil.getShipsCells(mShips);
		mMoveShipsAdapter = new MoveShipsAdapter(this, shipCells);

		mRule = new CellForbiddingRuleSquareImpl(Game.BOARD_SIDE);
		mForbiddenPositions = new ForbiddenPositionsManager();
		for (int i : shipCells) {
			List<Integer> forbiddenCells = mRule.getForbiddenCells(i);
			for (int fc : forbiddenCells) {
				mForbiddenPositions.addForbiddenCell((short) fc);
			}
		}
		
		mBoard = new short[Game.BOARD_SIDE * Game.BOARD_SIDE];
		for (int i = 0; i < mBoard.length; i++)
			mBoard[i] = Game.WATER;
		for (int shipIndex = 0; shipIndex < mShips.length; shipIndex++) {
			Ship tmpShip = mShips[shipIndex];
			for (int shipCell : tmpShip.getBoardCells()) {
				mBoard[shipCell] = (short) (shipIndex + 1);
			}
		}
		
		mSelectedShip = mOriginPosition = null;

		displayGameScreen();
		attachActionListeners();		
	}
	
	/**
	 * Restores the state of the arrange mShips board as it was before 
	 * the app was killed 
	 * @param oldState state before app was killed
	 */
	private void restoreState(Bundle oldState) {
		mSelectedShipIndex = -1;
		
		mShips = (Ship[]) oldState.getSerializable(BUNDLE_SHIPS);
		int[] shipsFields = ShipUtil.getShipsCells(mShips);
		mMoveShipsAdapter = new MoveShipsAdapter(this, shipsFields);
		
		mRule = new CellForbiddingRuleSquareImpl(Game.BOARD_SIDE);
		
		mForbiddenPositions = (ForbiddenPositionsManager) oldState.getSerializable(BUNDLE_FORBIDDEN_POSITION);
		mBoard = (short[]) oldState.getSerializable(BUNDLE_BOARD);
		mSelectedShip = null;
		mOriginPosition = null;
//		mLastPossiblePosition = null;
		
	}

	/**
	 * This function paints the cells of a ship that is being moved or restores
	 * the previous state of board cells when the ship is moved to a new
	 * position.
	 * 
	 * @param ship
	 *            holds the ship cells that are to be painted
	 * @param defaultColor
	 *            the color that is used to paint when the cell isn't
	 *            exceptional
	 * @param alternativeColor
	 *            the color that is used to paint when the field is exceptional
	 * @param action
	 * @return If there is a forbidden field in the ship fields than the
	 *         function returns true
	 */
	private boolean paint(ShipCellsHolder ship, int defaultColor, int alternativeColor, PaintAction action) {
		Ship tmpShip = ShipRepresentationTransformer.getShipFromShipCellsHolder(ship);
		boolean notPossibleShipPosition = false;
		ImageView iv;

		for (int position : tmpShip.getBoardCells()) {
			iv = (ImageView) mMoveShipsAdapter.getItem(position);

			if (action == PaintAction.PAINT_OVER) {
				if (mForbiddenPositions.isForbidden((short) position)) {
					((ImageView) iv).setImageResource(alternativeColor);
					notPossibleShipPosition = true;
				} else
					((ImageView) iv).setImageResource(defaultColor);
			}
			if (action == PaintAction.RESTORE) {
				if (mBoard[position] != 0)
					((ImageView) iv).setImageResource(alternativeColor);
				else
					((ImageView) iv).setImageResource(defaultColor);
			}
		}
		return notPossibleShipPosition;
	}

	/**
	 * Update Board when moving selected ship to new position
	 * 
	 * @param updatedCells	cells which is updated
	 * @param value			new value for updated cells
	 */
	private void updateBoard(int[] updatedCells, int value) {		
		for (int cell : updatedCells) {
			mBoard[cell] = (short) value;
		}
	}

	/**
	 * Description of calculatePosition This function calculates a position
	 * given coordinates from an onTouch event. It takes into account the size
	 * of the board, the touched field of the ship being moved and the size of
	 * the ship. If a horizontal ship with size 5 is being moved (dragged) by
	 * its top left field and the user's cursor gets next to the left border of
	 * the board this function shall return a position that is as if his cursor
	 * was 5 fields to the left.
	 * 
	 * @param coordinateX
	 *            the x coordinate of the user's cursor
	 * @param coordinateY
	 *            the x coordinate of the user's cursor
	 * @return the calculated position of the user's cursor in a linear massive
	 */
	protected short calculatePosition(float coordinateX, float coordinateY) {
		short x, y;
		short leftBorder;
		short rightBorder;
		short topBorder;
		short bottomBorder;
		// TODO : getPadding() should be getVerticalSpacing, but I don't know
		// how to get it. There is a set method but
		// no get method. Need research in google.

		int imageWidth = (boardGrid.getWidth() - (Game.BOARD_SIDE + 1)
				* boardGrid.getPaddingLeft())
				/ Game.BOARD_SIDE;
		int imageHeight = (boardGrid.getHeight() - (Game.BOARD_SIDE + 1)
				* boardGrid.getPaddingTop())
				/ Game.BOARD_SIDE;

		x = (short) (coordinateX / (imageWidth + boardGrid.getPaddingLeft()));
		y = (short) (coordinateY / (imageHeight + boardGrid.getPaddingTop()));
		topBorder = 0;
		bottomBorder = Game.BOARD_SIDE - 1;
		leftBorder = 0;
		rightBorder = Game.BOARD_SIDE - 1;

		if (mSelectedShip != null) {
			if (mSelectedShip.getDirection() == Direction.HORIZONTAL) {
				leftBorder = mSelectedShipCell;
				rightBorder = (short) (Game.BOARD_SIDE
						- mSelectedShip.getLength() + mSelectedShipCell);
			} else {
				topBorder = mSelectedShipCell;
				bottomBorder = (short) (Game.BOARD_SIDE
						- mSelectedShip.getLength() + mSelectedShipCell);
			}
		}

		if (x < leftBorder)
			x = leftBorder;
		if (x > rightBorder)
			x = rightBorder;
		if (y < topBorder)
			y = topBorder;
		if (y > bottomBorder)
			y = bottomBorder;

		/*if (y * Game.BOARD_SIDE + x < 0) {
			int i = 0;
			i++;
		}*/

		return (short) (y * Game.BOARD_SIDE + x);
	}

	private enum PaintAction {
		PAINT_OVER, RESTORE
	}
	
	/**
	 * The settings are saved in a Bundle.
	 * When the application become active again the settings will be
	 * loaded from the bundle
	 */
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putSerializable(BUNDLE_SHIPS, mShips);
		outState.putSerializable(BUNDLE_FORBIDDEN_POSITION, mForbiddenPositions);
		outState.putSerializable(BUNDLE_BOARD, mBoard);				
	}
			
	/**
	 * Displays the game screen
	 */
	private void displayGameScreen() {
		setContentView(R.layout.arrange_ships);
		Log.d("GAME", "displayGameScreen");		
		
		btnGo = (Button) findViewById(R.id.go_Button);
		btnCancel = (Button) findViewById(R.id.cancel_Button);
		btnRotate = (Button) findViewById(R.id.rotateShip_Button);
		boardGrid = (GridView) findViewById(R.id.GridViewMoveShips);		
		
		boardGrid.setAdapter(mMoveShipsAdapter);
	}
	
	/**
	 * attaches action listeners to:
	 * touch events
	 * clicking the buttons
	 */
	private void attachActionListeners() {

		boardGrid.setOnTouchListener(new OnTouchListener() {

			public boolean onTouch(View v, MotionEvent event) {
				short position;
				Log.d("GAME", "boardGrid is clicked");
				
				position = calculatePosition(event.getX(), event.getY());

				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					if (mBoard[position] != 0)
						manageOnTouchDown(position);
					break;
				case MotionEvent.ACTION_MOVE:
					if (mSelectedShip != null)
						manageOnTouchMove(position);
					break;
				case MotionEvent.ACTION_UP:
					if (mSelectedShip != null)
						manageOnTouchUp(position);
					break;
				}
				return false;
			}
		});

		btnGo.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {				
				Log.d("GAME", "btnGo is clicked");
				
				Bundle b = new Bundle();
				ActivityShipComunicator comm = new ActivityShipComunicator(mShips);
				b.putSerializable(SERIAL_INFO, comm);
				Intent intent = new Intent(ArrangeShipsActivity.this, BattleshipActivity.class);
				intent.putExtra(EXTRA_INFO, b);
				if (username.compareTo("") != 0) {
					intent.putExtra(UserInfoActivity.EXT_USERNAME, username);
				}
				/*
				intent.putExtra(EXTRA_FORMATION, mShips);*/
				startActivity(intent);
				
				finish();
			}
		});
		
		btnRotate.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				rotate(v);
				Log.d("GAME", "btnRotate is clicked");
			}

		});

		

		btnCancel.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				setResult(BACK, new Intent());
				finish();
			}
		});
	}
}
